Skip to content

LCORE-2631: Generate graphs#2003

Merged
tisnik merged 1 commit into
lightspeed-core:mainfrom
tisnik:lcore-2631-generate-graphs
Jun 26, 2026
Merged

LCORE-2631: Generate graphs#2003
tisnik merged 1 commit into
lightspeed-core:mainfrom
tisnik:lcore-2631-generate-graphs

Conversation

@tisnik

@tisnik tisnik commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Description

LCORE-2631: Generate graphs

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

  • Assisted-by: N/A
  • Generated by: N/A

Related Tickets & Documents

  • Related Issue #LCORE-2631

Summary by CodeRabbit

  • New Features

    • Added additional vulnerability report charts, including fix-time trends, most impacted packages, and new CVE date timelines.
    • Expanded report generation to produce all available graphs in one step when chart output is enabled.
  • Bug Fixes

    • Removed extra debug output from report generation for cleaner command-line logs.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tisnik, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 56 minutes and 11 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1b71d3b7-b586-43e9-a1f5-9e99a8cfffbf

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd7604 and a769671.

📒 Files selected for processing (1)
  • scripts/vulnerability_report.py

Walkthrough

The vulnerability report script now adds graphs for fix latency, vulnerable packages, and CVE dates, and main() conditionally runs graph generation when enabled.

Changes

Vulnerability report graph generation

Layer / File(s) Summary
Graph helpers and orchestrator
scripts/vulnerability_report.py
Adds histogram, bar, and line graph generators, plus a wrapper that calls the existing state and severity graph functions with the new outputs.
CLI graph trigger
scripts/vulnerability_report.py
main() now checks args.generate_graphs and calls generate_graphs(stat, prefix, args.svg_output, args.png_output).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely matches the main change: adding graph generation support.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tisnik tisnik force-pushed the lcore-2631-generate-graphs branch from 9fd7604 to a769671 Compare June 26, 2026 06:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/vulnerability_report.py`:
- Around line 529-556: The graph helpers crash when their Counter inputs are
empty because generate_new_cve_dates_graph and the package bar-chart block both
unpack zip(*) results unconditionally. Update the CVE dates and packages graph
generation paths to detect empty data before calling zip/unpacking, and in that
case render the existing “No data” placeholder instead of continuing. Use the
generate_new_cve_dates_graph function and the packages graph block that reads
stat["dates"] and stat["packages"] as the locations to apply the guard.
- Around line 507-536: The two plotting helpers, generate_fixed_in_day_graph and
generate_vulnerable_packages_graph, are hard-capping the y-axis with
ax.set_ylim(top=100), which can truncate high-count data. Remove that fixed
limit from both functions so matplotlib can autoscale based on the actual
values, while keeping the rest of the chart setup unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c566847d-0028-4438-9d8c-0a3a743a23f2

📥 Commits

Reviewing files that changed from the base of the PR and between 62251cf and 9fd7604.

📒 Files selected for processing (1)
  • scripts/vulnerability_report.py
📜 Review details
⏰ Context from checks skipped due to timeout. (15)
  • GitHub Check: Pylinter
  • GitHub Check: unit_tests (3.12)
  • GitHub Check: unit_tests (3.13)
  • GitHub Check: build-pr
  • GitHub Check: integration_tests (3.12)
  • GitHub Check: integration_tests (3.13)
  • GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: E2E: library mode / ci / group 1
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E Tests for Lightspeed Evaluation job
  • GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-0-6-on-pull-request
⚠️ CI failures not shown inline (2)

GitHub Actions: OpenAPI (Spectral) / 0_spectral.txt: LCORE-2631: Generate graphs

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1muv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json�[0m
 �[36;1mif ! diff -u docs/openapi.json /tmp/openapi-generated.json; then�[0m
 �[36;1m  echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"�[0m

GitHub Actions: OpenAPI (Spectral) / spectral: LCORE-2631: Generate graphs

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1muv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json�[0m
 �[36;1mif ! diff -u docs/openapi.json /tmp/openapi-generated.json; then�[0m
 �[36;1m  echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"�[0m
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-06-24T13:45:37.249Z
Learnt from: Jdubrick
Repo: lightspeed-core/lightspeed-stack PR: 1971
File: src/utils/markdown_repair.py:31-36
Timestamp: 2026-06-24T13:45:37.249Z
Learning: In the lightspeed-stack repository, docstrings must use the section header name "Parameters:" (not "Args:") for function arguments, even if the project references Google Python docstring conventions. Ensure docstrings follow the project’s established "Parameters:" header format for any documented function parameters.

Applied to files:

  • scripts/vulnerability_report.py

Comment on lines +507 to +536
fig, ax = plt.subplots()
D = stat["days"]["days"]
ax.hist(D, bins=30, edgecolor="black")
ax.set_ylim(top=100)
ax.set_xlabel("Days")
ax.set_title("Fixed in day(s)")
save_graph(fig, prefix, "days", svg_output, png_output)


def generate_vulnerable_packages_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
"""
Generate a bar chart showing the top 10 packages with the most CVEs.

Parameters:
stat (dict[str, Any]): Statistics dictionary containing vulnerability
data, with "packages" key holding package frequency counts.
prefix (str): Prefix for the output file name.
svg_output (bool): If true, save the graph as SVG.
png_output (bool): If true, save the graph as PNG.
"""
fig, ax = plt.subplots()
D = stat["packages"]
names, counts = zip(*D.most_common(10))
ax.bar(names, counts, edgecolor="black")
ax.set_ylim(top=100)
ax.set_title("CVEs per package")
ax.tick_params(axis="x", labelrotation=90)
fig.tight_layout()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

Remove the fixed top=100 y-axis limit.

The hard-coded ax.set_ylim(top=100) in both graph functions truncates data when vulnerability counts exceed 100. Popular packages frequently accumulate hundreds of CVEs, and large repositories produce histogram bin counts well above this threshold. This arbitrary cap renders charts misleading for datasets where the visualization is most critical. Letting matplotlib autoscale ensures accuracy across all data sizes.

Proposed fix
 def generate_vuln_for_days_graph(
     stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
 ) -> None:
     (...)
     D = stat["days"]["days"]
     ax.hist(D, bins=30, edgecolor="black")
-    ax.set_ylim(top=100)
     ax.set_xlabel("Days")
     ax.set_title("Fixed in day(s)")
     save_graph(fig, prefix, "days", svg_output, png_output)


 def generate_vulnerable_packages_graph(
     stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
 ) -> None:
     (...)
     D = stat["packages"]
     names, counts = zip(*D.most_common(10))
     ax.bar(names, counts, edgecolor="black")
-    ax.set_ylim(top=100)
     ax.set_title("CVEs per package")
     ax.tick_params(axis="x", labelrotation=90)
     fig.tight_layout()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fig, ax = plt.subplots()
D = stat["days"]["days"]
ax.hist(D, bins=30, edgecolor="black")
ax.set_ylim(top=100)
ax.set_xlabel("Days")
ax.set_title("Fixed in day(s)")
save_graph(fig, prefix, "days", svg_output, png_output)
def generate_vulnerable_packages_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
"""
Generate a bar chart showing the top 10 packages with the most CVEs.
Parameters:
stat (dict[str, Any]): Statistics dictionary containing vulnerability
data, with "packages" key holding package frequency counts.
prefix (str): Prefix for the output file name.
svg_output (bool): If true, save the graph as SVG.
png_output (bool): If true, save the graph as PNG.
"""
fig, ax = plt.subplots()
D = stat["packages"]
names, counts = zip(*D.most_common(10))
ax.bar(names, counts, edgecolor="black")
ax.set_ylim(top=100)
ax.set_title("CVEs per package")
ax.tick_params(axis="x", labelrotation=90)
fig.tight_layout()
fig, ax = plt.subplots()
D = stat["days"]["days"]
ax.hist(D, bins=30, edgecolor="black")
ax.set_xlabel("Days")
ax.set_title("Fixed in day(s)")
save_graph(fig, prefix, "days", svg_output, png_output)
def generate_vulnerable_packages_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
"""
Generate a bar chart showing the top 10 packages with the most CVEs.
Parameters:
stat (dict[str, Any]): Statistics dictionary containing vulnerability
data, with "packages" key holding package frequency counts.
prefix (str): Prefix for the output file name.
svg_output (bool): If true, save the graph as SVG.
png_output (bool): If true, save the graph as PNG.
"""
fig, ax = plt.subplots()
D = stat["packages"]
names, counts = zip(*D.most_common(10))
ax.bar(names, counts, edgecolor="black")
ax.set_title("CVEs per package")
ax.tick_params(axis="x", labelrotation=90)
fig.tight_layout()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vulnerability_report.py` around lines 507 - 536, The two plotting
helpers, generate_fixed_in_day_graph and generate_vulnerable_packages_graph, are
hard-capping the y-axis with ax.set_ylim(top=100), which can truncate high-count
data. Remove that fixed limit from both functions so matplotlib can autoscale
based on the actual values, while keeping the rest of the chart setup unchanged.

Comment on lines +529 to +556
fig, ax = plt.subplots()
D = stat["packages"]
names, counts = zip(*D.most_common(10))
ax.bar(names, counts, edgecolor="black")
ax.set_ylim(top=100)
ax.set_title("CVEs per package")
ax.tick_params(axis="x", labelrotation=90)
fig.tight_layout()
save_graph(fig, prefix, "packages", svg_output, png_output)


def generate_new_cve_dates_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
"""
Create a line chart showing new CVE detection dates over time.

Parameters:
stat (dict[str, Any]): Statistics dictionary with "dates" key
containing a Counter of datetime objects mapped to occurrence counts.
prefix (str): Base filename prefix for output files.
svg_output (bool): Whether to save the graph as SVG.
png_output (bool): Whether to save the graph as PNG.
"""
fig, ax = plt.subplots()
D = stat["dates"]
dates, counts = zip(*sorted(D.items(), key=lambda x: x[0]))
ax.plot(dates, counts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

Fix empty-counter crash in graph generation helpers.

Lines 531 and 555 attempt to unpack zip(*) results without checking if the input Counter is empty. Since Counter.most_common(10) and sorted(Counter.items()) return empty lists when the counter has no items, zip(*[]) produces an empty iterator. Unpacking this into names, counts raises ValueError: not enough values to unpack, causing the CLI to crash on valid empty Dependabot reports when --generate-graphs is enabled.

Guard against empty data explicitly before unpacking to emit a "No data" placeholder graph instead.

Proposed fix
 def generate_vulnerable_packages_graph(
     stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
 ) -> None:
@@
     fig, ax = plt.subplots()
     D = stat["packages"]
+    if not D:
+        ax.set_title("CVEs per package")
+        ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes)
+        save_graph(fig, prefix, "packages", svg_output, png_output)
+        return
     names, counts = zip(*D.most_common(10))
@@
 def generate_new_cve_dates_graph(
     stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
 ) -> None:
@@
     fig, ax = plt.subplots()
     D = stat["dates"]
+    if not D:
+        ax.set_title("New CVEs timeline")
+        ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes)
+        save_graph(fig, prefix, "timeline", svg_output, png_output)
+        return
     dates, counts = zip(*sorted(D.items(), key=lambda x: x[0]))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fig, ax = plt.subplots()
D = stat["packages"]
names, counts = zip(*D.most_common(10))
ax.bar(names, counts, edgecolor="black")
ax.set_ylim(top=100)
ax.set_title("CVEs per package")
ax.tick_params(axis="x", labelrotation=90)
fig.tight_layout()
save_graph(fig, prefix, "packages", svg_output, png_output)
def generate_new_cve_dates_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
"""
Create a line chart showing new CVE detection dates over time.
Parameters:
stat (dict[str, Any]): Statistics dictionary with "dates" key
containing a Counter of datetime objects mapped to occurrence counts.
prefix (str): Base filename prefix for output files.
svg_output (bool): Whether to save the graph as SVG.
png_output (bool): Whether to save the graph as PNG.
"""
fig, ax = plt.subplots()
D = stat["dates"]
dates, counts = zip(*sorted(D.items(), key=lambda x: x[0]))
ax.plot(dates, counts)
fig, ax = plt.subplots()
D = stat["packages"]
if not D:
ax.set_title("CVEs per package")
ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes)
save_graph(fig, prefix, "packages", svg_output, png_output)
return
names, counts = zip(*D.most_common(10))
ax.bar(names, counts, edgecolor="black")
ax.set_ylim(top=100)
ax.set_title("CVEs per package")
ax.tick_params(axis="x", labelrotation=90)
fig.tight_layout()
save_graph(fig, prefix, "packages", svg_output, png_output)
def generate_new_cve_dates_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
"""
Create a line chart showing new CVE detection dates over time.
Parameters:
stat (dict[str, Any]): Statistics dictionary with "dates" key
containing a Counter of datetime objects mapped to occurrence counts.
prefix (str): Base filename prefix for output files.
svg_output (bool): Whether to save the graph as SVG.
png_output (bool): Whether to save the graph as PNG.
"""
fig, ax = plt.subplots()
D = stat["dates"]
if not D:
ax.set_title("New CVEs timeline")
ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes)
save_graph(fig, prefix, "timeline", svg_output, png_output)
return
dates, counts = zip(*sorted(D.items(), key=lambda x: x[0]))
ax.plot(dates, counts)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vulnerability_report.py` around lines 529 - 556, The graph helpers
crash when their Counter inputs are empty because generate_new_cve_dates_graph
and the package bar-chart block both unpack zip(*) results unconditionally.
Update the CVE dates and packages graph generation paths to detect empty data
before calling zip/unpacking, and in that case render the existing “No data”
placeholder instead of continuing. Use the generate_new_cve_dates_graph function
and the packages graph block that reads stat["dates"] and stat["packages"] as
the locations to apply the guard.

@tisnik tisnik merged commit 19d6a85 into lightspeed-core:main Jun 26, 2026
29 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant